﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Runtime.Caching;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Diagnostics;

namespace Ruandao.MiniFace
{

    public class WidgetElement : Content
    {
        // 微件的类型的出现在标记中的别名，别名对应的类型全名在TemplateTypes.xml中定义。
        public string AliasName { get; private set; }

        // 微件的类型
        public Type WidgetType { get; set; }

        // 微件的预定义模板的名称
        public string TemplateName { get; private set; }

        // 属性字符串中可能包括了字段、表达式或者是静态字符
        // 字典中的一项表示一个属性的描述。
        // 这些属性对应了方法的参数集合。
        public Dictionary<string, List<ValueItem>> Attributes { get; set; }

        // 微件的呈现模板，如果模板中使用了{? TemplateName ?}的占位符，那么将用子模板来渲染。
        public List<Content> Contents { get; set; }

        // 子模板，使用<? TemplateName> ... </Template?>表示。
        // 子模板的占位符使用{? TemplateName ?}来表示。
        public Dictionary<string, List<Content>> TemplateItems { get; set; }

        public WidgetElement(Template template, string text)
            :base(template)
        {
            Attributes = new Dictionary<string, List<ValueItem>>();
            TemplateName = "";
            string body = "";
            if (text.EndsWith("/>"))
            {
                ParseAttributes(text);

                string dirBase;
                if (Template.MapPath != null)
                    dirBase = Template.MapPath(template.TemplateUrlBase);
                else
                    dirBase = HttpContext.Current.Server.MapPath(template.TemplateUrlBase);

                string path = Ruandao.Misc.UtilityFunction.CombinePath(dirBase, "Widget/" + this.AliasName + ".wgt");
                string pattern = "<\\$" + this.TemplateName + ".*?" + this.TemplateName + "\\$>";

                body = GetTemplateBody(path, pattern);
            }
            else
            {
                int start = text.IndexOf('>');
                ParseAttributes(text.Substring(0, start) + "/>");

                int length = text.LastIndexOf("</") - start - 1;
                body = text.Substring(start + 1, length);
            }

            Contents = new List<Content>();
            TemplateItems = new Dictionary<string, List<Content>>();
            ParseBody(body);
        }

        protected void ParseBody(string bodyText)
        {
            string pattern = @"\{\*.*?\*\}|\{=.*?=\}|\{%.*?%\}|\{\?.*?\?\}|<\?.*?\?>";
            foreach (var segment in new Traversal(bodyText, pattern))
            {
                if (!segment.IsTarget)
                {
                    Contents.Add(new TextItem(this.Template,segment.Text));
                    continue;
                }
                string prefix = segment.Text.Substring(0, 2);
                string middle = segment.Text.Substring(2, segment.Text.Length - 4);
                switch (prefix)
                {
                    case "{*":
                        Contents.Add(new CommentText(this.Template, middle));
                        break;
                    case "{=":
                        Contents.Add(new ExpressionItem(this.Template, middle));
                        break;
                    case "{%":
                        Contents.Add(new ScriptBlock(this.Template, middle));
                        break;
                    case "{?":
                        Contents.Add(new TemplateItem(this.Template, middle));
                        break;
                    case "<?":
                        int start = middle.IndexOf(">");
                        string name = middle.Substring(0, start);
                        int length = middle.LastIndexOf("</") - ( start + 1 );
                        string itemText = middle.Substring(start + 1, length);

                        this.TemplateItems.Add(name.Trim(), ParseWidgetItem(itemText));
                        break;
                }

            }
        }

        private List<Content> ParseWidgetItem(string html)
        {
            var contents = new List<Content>();
            string pattern = @"\{\*.*?\*\}|\{=.*?=\}|\{%.*?%\}|\{\?.*?\?\}";
            foreach (var segment in new Traversal(html, pattern))
            {
                if (!segment.IsTarget)
                {
                    contents.Add(new TextItem(this.Template, segment.Text));
                    continue;
                }
                string prefix = segment.Text.Substring(0, 2);
                string middle = segment.Text.Substring(2, segment.Text.Length - 4);
                switch (prefix)
                {
                    case "{*":
                        contents.Add(new CommentText(this.Template, middle));
                        break;
                    case "{=":
                        contents.Add(new ExpressionItem(this.Template, middle));
                        break;
                    case "{%":
                        contents.Add(new ScriptBlock(this.Template, middle));
                        break;
                    case "{?":
                        contents.Add(new TemplateItem(this.Template, middle.Trim()));
                        break;
                }

            }
            return contents;
        }

        public override void Render(TemplateContext templateContext, System.IO.TextWriter writer)
        {
                // 把参数集合传递微件，
                // 如产品微件，则传递产品的id等，有微件根据参数到数据库中获取微件的数据模型。
                var properties = new Dictionary<string, string>();
                foreach (var attr in this.Attributes)
                {
                    string value = "";
                    foreach (var item in attr.Value)
                    {
                        value += item.GetValue(templateContext);
                    }
                    properties.Add(attr.Key, value);
                }

                WidgetBase widget = (WidgetBase)Activator.CreateInstance(this.WidgetType);
                widget.Properties = properties;
                widget.WidgetTemplate = this;
            try
            {
                widget.Render(templateContext, writer);
            }
            catch (Exception ex)
            {
                GetExceptionMessage(ex);
            }
        }

        [Conditional("DEBUG")]
        private void GetExceptionMessage(Exception ex)
        {
            StringBuilder message = new StringBuilder();
            message.AppendLine("错误信息:" + ex.Message);
            message.AppendLine("错误源：" + ex.ToString() + "\n");
            message.AppendLine("微件类型别名：" + AliasName);
            message.AppendLine("微件类型：" + WidgetType);
            message.AppendLine("微件预定义模板名称：" + TemplateName + "\n");
            foreach (var item in Attributes)
            {
                message.AppendLine("属性描述：" + item.Key);
                message.Append("模板字符、字段或者表达式的集合：");
                foreach (ValueItem va in item.Value)
                {
                    message.Append(va.ToString() + ";");
                }
                message.AppendLine();
            }
            message.AppendLine("\n内容项集合：");
            foreach (Content con in Contents)
            {
                message.AppendLine(con.ToString());
            }
            message.AppendLine();
            foreach (var item in TemplateItems)
            {
                message.AppendLine("子模板项：");
                message.AppendLine(item.Key);
                message.AppendLine("模板字符、字段或者表达式的集合：");
                foreach (Content con in item.Value)
                {
                    message.AppendLine(con.ToString());
                }
                message.AppendLine();
            }
            ex = new Exception(message.ToString());
            throw ex;
        }

        private string GetTemplateBody(string path, string pattern)
        {
            string htmlText;
            using (StreamReader reader = new StreamReader(path))
            {
                htmlText = reader.ReadToEnd();
            }

            // 替换模板路径和应用程序路径的占位符。
            htmlText.Replace("{~}/", VirtualPathUtility.ToAbsolute("~/"));
            htmlText.Replace("{^}/", VirtualPathUtility.ToAbsolute(this.Template.TemplateUrlBase));
            // 下面的做法和ie保存网页的做法一致，也是使用.htm和_files的命名约定。
            string url = Ruandao.Misc.UtilityFunction.CombineUrl(this.Template.TemplateUrlBase, this.Template.FilePath);
            string ext = ".htm";
            if (url.EndsWith(".html")) ext = ".html";
            htmlText.Replace("{.}/", url.Replace(ext, "_files/"));

            MatchCollection matches = Regex.Matches(htmlText, pattern, RegexOptions.Singleline);
            string body = "";
            if (matches.Count > 0)
            {
                body = matches[0].Value.Substring(2, matches[0].Length - 4);
                body = "<" + body + ">";
                int start = body.IndexOf('>');
                int length = body.LastIndexOf("</") - start - 1;
                body = body.Substring(start + 1, length);
            }
            return body;
        }

        private void ParseAttributes(string elementText)
        {
            XElement element = XElement.Parse(elementText);
            this.AliasName = element.Name.LocalName;
            this.WidgetType = GetType(element.Name.LocalName);
            this.TemplateName = "";
            foreach (var attr in element.Attributes())
            {
                string parameter = attr.Name.LocalName;
                string value = attr.Value;
                if (parameter.ToLower() == "name")
                {
                    this.TemplateName = value;
                    continue;
                }

                this.Attributes.Add(parameter, ParseAttribute(value));
            }

        }

        private List<ValueItem> ParseAttribute(string text)
        {
            List<ValueItem> values = new List<ValueItem>();

            string pattern = @"\{=.*?=\}";
            foreach (var segment in new Traversal(text, pattern))
            {
                if (!segment.IsTarget)
                {
                    values.Add(new TextItem(this.Template, segment.Text));
                    continue;
                }
                string prefix = segment.Text.Substring(0, 2);
                string middle = segment.Text.Substring(2, segment.Text.Length - 4);
                switch (prefix)
                {
                    case "{=":
                        values.Add(new ExpressionItem(this.Template, middle));
                        break;
                }

            }
            return values;
        }

        private static Type GetType(string typeString)
        {

            Dictionary<string, string> typeNames = getTypeNames();

            Type type;
            if (typeNames.ContainsKey(typeString))
            {
                type = Type.GetType(typeNames[typeString]);
                return type;
            }
            throw new NotSupportedException("微件的类型名称出错，请检查TemplateTypes.xml中的设置。" + typeString);
        }

        private static Dictionary<string, string> getTypeNames()
        {
            ObjectCache cache = MemoryCache.Default;
            Dictionary<string, string> types = cache["TEMPLATE_HELPER_WIDGET_TYPES"] as Dictionary<string, string>;

            if (types == null)
            {
                string filePath = HttpContext.Current.Server.MapPath("~/App_Data/TemplateTypes.xml");

                CacheItemPolicy policy = new CacheItemPolicy();
                List<string> filePaths = new List<string>();
                filePaths.Add(filePath);

                policy.ChangeMonitors.Add(new HostFileChangeMonitor(filePaths));

                // 解析模板文件.
                types = ParseTypesXmlFile(filePath);

                cache.Set("TEMPLATE_HELPER_WIDGET_TYPES", types, policy);
            }
            return types;
        }

        // 文件格式：
        // <types>
        //     <type alias="" type="" /> ...
        // </types>
        /// <summary>
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private static Dictionary<string, string> ParseTypesXmlFile(string filePath)
        {
            var types = new Dictionary<string, string>();
            XDocument document = XDocument.Load(filePath);
            foreach (var element in document.Root.Elements("type"))
            {
                string alias = element.Attribute("alias").Value;
                string type = element.Attribute("type").Value; ;
                types.Add(alias, type);
            }
            return types;
        }

    }
}
